%%%---------------------------------------------------------------------------------------
%%% @author     Bhasker V Kode <kode@returnable.org>
%%% @copyright  2007 Bhasker V Kode
%%% @doc        Module for load-balancing and handling distributed spawn creation across multiple recognized nodes
%%% @reference  See <a href="http://bspawner.googlecode.com" target="_top">http://bspawner.googlecode.com</a> for more information.
%%% Sourcecode from http://www.erlang.org/doc/getting_started/conc_prog.html located on <a href="http://erlang.org">erlang.org</a>
%%% included in this project fall under the <a href="http://www.erlang.org/EPLICENSE">http://www.erlang.org/EPLICENSE</a> licence .
%%% @end
 
%%%---------------------------------------------------------------------------------------

-module(bspawner).
-author('bosky101@gmail.com').

%% bspawner API
-export([
	 bmessage/2, %% alternative API for mesage,but balanced
 	 bspawn/1,  %% can balance any M/F/A across nodes
	 start_bspawner/0, %% alias handlers
	 bspawner_state_loop/2, %%concurrency handlers
	 indexof/2,moduloN/2 %% utilities
	 
]).

%% messenger/message-passing API
-export([start_server/0, server/1, logon/1, logoff/0, message/2, client/2]).


%%--------------------------------------------------------------------
%% @spec () -> any()
%% @doc Required to set aliases for process's
%% @end 
%%--------------------------------------------------------------------
start_bspawner()->
     A = spawn(?MODULE,bspawner_state_loop,[[],{0,0}]),
    global:unregister_name(spawn_bspawner),
    _AR= global:register_name(spawn_bspawner,A).

%%--------------------------------------------------------------------
%% @doc balanced-spawn creating function with API similiar to 
%% messenger:message(User,Message)
%% @end 
%%--------------------------------------------------------------------
bmessage(To,Data)->
    ?MODULE:bspawn({bspawner_request,{?MODULE,message,Data}}).

%%--------------------------------------------------------------------
%% @doc  balanced-spawn creating function for any {Module,Function,Args}
%% @end 
%%--------------------------------------------------------------------
bspawn(Args)->
    global:send(spawn_bspawner,{self(),Args}),
    receive
	_R ->
	    io:format("~n back in bspawner with result= ~p",[_R])	 
    end,
    io:format("~nbspawner done",[]).

%%--------------------------------------------------------------------
%% @doc  maintains state information required to load-balance in a 
%% round-robin manner
%% @end 
%%--------------------------------------------------------------------
bspawner_state_loop(NodesList,{N,Index}) ->
    receive
	{From,{reset}} ->
	    From!{ok},
	    bspawner_state_loop([],{0,0});
	{From,{add_node,Node}} ->
	    From!{ok},
	    bspawner_state_loop([NodesList|Node],{N+1,Index});
   	{From,{remove_node,Node}} ->
	    %%Not implemented
	    %%TODO implementation for removing node from nodelist
	    EditedNodes = NodesList,
  	    From!{ok},
            bspawner_state_loop(EditedNodes,{N-1,Index});
	{From,{simulate}}->
	    %%io:format("~n nodeslist is ~p",[NodesList]),
	    From!{ok},
	    bspawner_state_loop([servernode1,node1],{2,0});	   
	{From,{list}}->
	    io:format("~n nodeslist is ~p",[NodesList]),
	    From!{ok},
	    bspawner_state_loop(NodesList,{N,Index});
	%%main spawn load-balancing
	{From,{bspawn,{M,F,A}}} ->
	     TheChosenOne = indexof(NodesList, moduloN(Index,N) ),
	     ?MODULE:message(TheChosenOne, {bspawner_request,servernode1 ,{M,F,A}} ),
	     From!{ok,TheChosenOne},
	     bspawner_state_loop(NodesList,{N,Index+1});
	_Else  ->
	    io:format("~n in bspawner else",[]),
	    bspawner_state_loop(NodesList,{N,Index})
    end.
		    

%% utilities
%% @doc utilities : To be able to select one of N-1 elements for any X
%% @end
moduloN(X,N)->	  
    %%X rem N.
    %%OR
    %%X - ( (X div N)*N).
    X rem N.
    
%% utilities
%% @doc utilities : to get the nth element of a list
%% @end
indexof([],_)->
    0;

indexof(L,N)->
    indexof(L,N,0).   

indexof([H|T],N,Ctr) when N =:= Ctr ->
    H;

indexof([H|T],N,Ctr)->
    indexof(T,N,Ctr+1);

indexof([],N,Ctr) ->
    -1.

%%% Modified "messenger.erl"
%%% Original available from http://www.erlang.org/doc/getting_started/conc_prog.html
%%% Look for %%[bspawner]%% to indetify edits/modifications

%%% Message passing utility.  
%%% User interface:
%%% logon(Name)
%%%     One user at a time can log in from each Erlang node in the
%%%     system messenger: and choose a suitable Name. If the Name
%%%     is already logged in at another node or if someone else is
%%%     already logged in at the same node, login will be rejected
%%%     with a suitable error message.
%%% logoff()
%%%     Logs off anybody at at node
%%% message(ToName, Message)
%%%     sends Message to ToName. Error messages if the user of this 
%%%     function is not logged on or if ToName is not logged on at
%%%     any node.
%%%
%%% One node in the network of Erlang nodes runs a server which maintains
%%% data about the logged on users. The server is registered as "messenger"
%%% Each node where there is a user logged on runs a client process registered
%%% as "mess_client" 
%%%
%%% Protocol between the client processes and the server
%%% ----------------------------------------------------
%%% 
%%% To server: {ClientPid, logon, UserName}
%%% Reply {messenger, stop, user_exists_at_other_node} stops the client
%%% Reply {messenger, logged_on} logon was successful
%%%
%%% To server: {ClientPid, logoff}
%%% Reply: {messenger, logged_off}
%%%
%%% To server: {ClientPid, logoff}
%%% Reply: no reply
%%%
%%% To server: {ClientPid, message_to, ToName, Message} send a message
%%% Reply: {messenger, stop, you_are_not_logged_on} stops the client
%%% Reply: {messenger, receiver_not_found} no user with this name logged on
%%% Reply: {messenger, sent} Message has been sent (but no guarantee)
%%%
%%% To client: {message_from, Name, Message},
%%%
%%% Protocol between the "commands" and the client
%%% ----------------------------------------------
%%%
%%% Started: messenger:client(Server_Node, Name)
%%% To client: logoff
%%% To client: {message_to, ToName, Message}
%%%
%%% Configuration: change the server_node() function to return the
%%% name of the node where the messenger server runs



%%@doc
%% Change the function below to return the name of the node where the
%% messenger server runs
%%@end
server_node() ->
    %%[bspawner]%%
    servernode1@bhasker.

%% @doc
%% This is the server process for the "messenger"
%% the user list has the format [{ClientPid1, Name1},{ClientPid22, Name2},...]
%% @end
server(User_List) ->
    receive
        {From, logon, Name} ->
            New_User_List = server_logon(From, Name, User_List),
            server(New_User_List);
        {From, logoff} ->
            New_User_List = server_logoff(From, User_List),
            server(New_User_List);

	{From, message_to, To, Message} ->
            server_transfer(From, To, Message, User_List),
            io:format("~nserver:message_to:list is now: ~p~n", [User_List]),
            server(User_List);

	%%[bspawner]%%
	%%added custom guard
        {_From, bspawner_request, _To,{M,F,A}}->
	    io:format("~nserver:bspawner_request:list is now ~p~n",[User_List]),
	    global:send( spawn_bspawner,  {spawn,M,F,A} ),
	    server(User_List)

    end.

%% @doc
%% Start the messaging server
%% @end

start_server() ->
    %%[bspawner]%%
    %%setting M as ?MODULE rather than hardcoding
    register(messenger, spawn(?MODULE, server, [[]])),
    %%also adding start/alises required for spawn_bspawner
    start_bspawner().


%% @doc Server adds a new user to the user list
%% @end
server_logon(From, Name, User_List) ->
    %% check if logged on anywhere else
    case lists:keymember(Name, 2, User_List) of
        true ->
            From ! {messenger, stop, user_exists_at_other_node},  %reject logon
            User_List;
        false ->
            From ! {messenger, logged_on},
            [{From, Name} | User_List]        %add user to the list
    end.

%% @doc Server deletes a user from the user list
%% @end
server_logoff(From, User_List) ->
    lists:keydelete(From, 1, User_List).


%% @doc Server transfers a message between user
%% @end
server_transfer(From, To, Message, User_List) ->
    %% check that the user is logged on and who he is
    case lists:keysearch(From, 1, User_List) of
        false ->
            From ! {messenger, stop, you_are_not_logged_on};
        {value, {From, Name}} ->
            server_transfer(From, Name, To, Message, User_List)
    end.

%% @doc If the user exists, send the message
%% @end
server_transfer(From, Name, To, Message, User_List) ->
    %% Find the receiver and send the message
    case lists:keysearch(To, 2, User_List) of
        false ->
            From ! {messenger, receiver_not_found};
        {value, {ToPid, To}} ->
            ToPid ! {message_from, Name, Message}, 
            From ! {messenger, sent} 
    end.


%% @doc To add/check a user with the userlist
%% @end
logon(Name) ->
    case whereis(mess_client) of 
        undefined ->
            register(mess_client, 
                     spawn(?MODULE, client, [server_node(), Name]));

	    %%[bspawner]%%
	    %%global:send(spawn_bspawner, {add_node,Name});
        _ -> already_logged_on
    end.

%% @doc Logoff a user
%% @end
logoff() ->
    mess_client ! logoff.

%% @doc API to send message to a node
%% @end
message(ToName, Message) ->
    case whereis(mess_client) of % Test if the client is running
        undefined ->
            not_logged_on;
        _ -> mess_client ! {message_to, ToName, Message},
             ok
end.


%% @doc The client process which runs on each server node
%% @end
client(Server_Node, Name) ->
    {messenger, Server_Node} ! {self(), logon, Name},
    await_result(),
    client(Server_Node).

client(Server_Node) ->
    receive
        logoff ->
            {messenger, Server_Node} ! {self(), logoff},
            exit(normal);
        {message_to, ToName, Message} ->
            {messenger, Server_Node} ! {self(), message_to, ToName, Message},
            await_result();
	%%[bspawner]%%
	%% custom receive of M,F,A on target as decide by bspawner_state_loop%%
	{message_from,FromName, {bspawner_request,ToNode,{M,F,A} }= CustomMessage } ->
	    LocalResult = apply(M,F,A),
	    io:format("~nHURRAY!!!~nbspawner_request message was ~p, and its appled local result is ~p",[CustomMessage,LocalResult]);
	{message_from, FromName, Message} ->
	    io:format("~nclient:Message from ~p: ~p~n", [FromName, Message])

    end,
    client(Server_Node).

%% @doc wait for a response from the server
%% @end
await_result() ->
    receive
        {messenger, stop, Why} -> % Stop the client 
            io:format("~p~n", [Why]),
            exit(normal);
        {messenger, What} ->  % Normal response
            io:format("~p~n", [What])
    end.
    
